/** 注释
 * @name globalNotify
 * @Author FiveXu(316643470@qq.com)
 * @Description: globalNotify
 * @Date  2021-07-15 05:30
 */
import NotifyVue, { NotifyProps, NotifyPosition } from './notify.vue'
import { ComponentPublicInstance } from '@vue/runtime-core'
import { Emitter } from 'mitt'
import { randomId } from '@/base/utils'
import { createApp } from 'vue'
import { getZIndex } from '@/base/hooks'

// 通知服务可传入值
interface GlobalNotifyProps extends NotifyProps {
  duration: number;
  position: NotifyPosition;
}

// 通知VNode
interface NotifyInstance extends ComponentPublicInstance {
  closeNotify?: () => void;
  setCurrentTop?: (val: number) => void;
  notifyMitt?: Emitter;
}

// 通知列表记录当前定位数据
type NotifyRect = { top: number; height: number; bottom: number }
// 通知列表组 单独每个方向存储值
type NotifyArr = { id: string, app: NotifyInstance, floor: number, dom: HTMLElement, rect: NotifyRect, position: NotifyPosition }
// 全部通知数据组
type NotifyData = { 'top-right': NotifyArr[], 'top-left': NotifyArr[], 'bottom-right': NotifyArr[], 'bottom-left': NotifyArr[], }
// 通知数据
const notifyData: NotifyData = {
  'top-right': [],
  'top-left': [],
  'bottom-right': [],
  'bottom-left': []
}

// 默认通知消失时间
const NotifyDuration = 2000
// 默认通知起始点间距
const NotifyOffset = 20

class GlobalNotify {
  private readonly props: Partial<GlobalNotifyProps>
  private readonly position: NotifyPosition
  private notifyDom: HTMLElement | undefined
  private notifyApp: NotifyInstance | undefined
  private notifyId: string | undefined

  constructor (props: Partial<GlobalNotifyProps> | string = {}) {
    if (typeof props === 'string') {
      this.props = {
        message: props,
        duration: NotifyDuration
      }
      this.position = 'top-right'
    } else {
      this.props = Object.assign({ duration: NotifyDuration }, props)
      this.position = props.position || 'top-right'
    }
    this.initNotify()
  }

  private getRect (dom: HTMLElement): NotifyRect {
    if (!notifyData[this.position].length) {
      const rect = dom.children[0].getBoundingClientRect()
      return {
        top: this.props.offset || NotifyOffset,
        height: rect.height,
        bottom: this.props.offset || NotifyOffset
      }
    }
    const notifyArr = notifyData[this.position]
    const lastRect = notifyArr[notifyArr.length - 1].rect
    const top = lastRect.top + lastRect.height + NotifyOffset
    const bottom = lastRect.bottom + lastRect.height + NotifyOffset
    const { height } = dom.children[0].getBoundingClientRect()
    return {
      top,
      height,
      bottom
    }
  }

  private initNotify (): void {
    const notifyArr = notifyData[this.position]
    const floor = notifyArr[notifyArr.length - 1]?.floor + 1 || 0
    this.notifyDom = document.createElement('div')
    this.notifyDom.className = 'fx-notify-wrapper'
    this.notifyDom.style.zIndex = String(getZIndex())
    document.body.appendChild(this.notifyDom)
    this.notifyId = randomId()
    const props = Object.assign(this.props, { id: this.notifyId })
    this.notifyApp = createApp(NotifyVue, props).mount(this.notifyDom)
    notifyArr.push({
      dom: this.notifyDom,
      id: this.notifyId,
      app: this.notifyApp,
      floor: floor,
      rect: this.getRect(this.notifyDom),
      position: this.position
    })
    const top = notifyArr[notifyArr.length - 1].rect.top
    const bottom = notifyArr[notifyArr.length - 1].rect.bottom
    const isTop = props.position === 'top-left' || props.position === 'top-right'
    if (this.notifyApp?.setCurrentTop) this.notifyApp.setCurrentTop(isTop ? top : bottom)
    if (this.notifyApp?.notifyMitt) this.notifyApp.notifyMitt.on('close', this.close.bind(this))
    if (this.props.duration) setTimeout(this.close.bind(this), Math.abs(this.props.duration))
  }

  private close (): void {
    const notifyArr = notifyData[this.position]
    const currentNotify = notifyArr.find((item: NotifyArr) => item.id === this.notifyId)
    notifyArr.forEach((item: NotifyArr): void => {
      if (currentNotify && (currentNotify.floor <= item.floor)) {
        if (item.floor > 0) {
          item.rect.top -= (currentNotify.rect.height + NotifyOffset)
          item.rect.bottom -= (currentNotify.rect.height + NotifyOffset)
          item.floor--
        }
      }
    })
    notifyData[this.position] = notifyArr.filter((item: NotifyArr) => item.id !== this.notifyId)
    notifyArr.forEach((item: NotifyArr): void => {
      const isTop = item.position === 'top-left' || item.position === 'top-right'
      if (item.app.setCurrentTop) item.app.setCurrentTop(isTop ? item.rect.top : item.rect.bottom)
    })
    if (currentNotify?.app && currentNotify.app.closeNotify) currentNotify.app.closeNotify()
    setTimeout(() => {
      if (currentNotify?.dom) currentNotify.dom.remove()
    }, 300)
  }
}

export const Notify = (props?: Partial<GlobalNotifyProps> | string): GlobalNotify => new GlobalNotify(props)
Notify.primary = (mes: string) => {
  Notify({
    message: mes,
    type: 'primary'
  })
}
Notify.success = (mes: string) => {
  Notify({
    message: mes,
    type: 'success'
  })
}
Notify.warning = (mes: string) => {
  Notify({
    message: mes,
    type: 'warning'
  })
}
Notify.error = (mes: string) => {
  Notify({
    message: mes,
    type: 'error'
  })
}
